(ns web
  (:require [clojure.contrib.json :as json])
  (:require [clojure.contrib.seq-utils :as seq])
  (:require [clojure.contrib.str-utils2 :as str])
  (:import java.net.URLDecoder java.net.URLEncoder)
  (:use compojure.core, ring.adapter.jetty)
  (:use hiccup.core hiccup.page-helpers)
  (:require [compojure.route :as route])
)

(import '(java.util.concurrent BlockingQueue LinkedBlockingQueue TimeUnit))

(defn url-decode [param]
  (URLDecoder/decode param "iso-8859-1"))

(defn url-encode [param]
  (URLEncoder/encode param "iso-8859-1"))

(defn zip-str [coll del]
  (reduce #(format "%s%s%s" %1 del %2) coll))

(defn spacing [coll]
  (zip-str coll " "))

(defn zip-a [coll del]
  (reduce #(format "%s%s%s" (html %1) del (html %2)) coll))

(defn spacing-a [coll]
  (when (> (count coll) 0)
    (zip-a coll "&nbsp;&nbsp;")))

(defn newline-a [coll]
  (zip-a coll "<br />"))

(defn template [name]
  (slurp (str global/web-folder "/template/" name ".html")))

(defn js [script]
  (include-js (str "/js/" script ".js")))

(defn load-static-file []
  (route/files "/" {:root global/web-folder}))

(defn async-link [type cmd-name id display-name msg]
  (html [:a {"href" "" "class" cmd-name "id" id} display-name]
	[:script {"type" "text/javascript"} "
        $(document).ready(function() { 
        $(\"a." cmd-name "\").click(
	function () {
	    $.post(\"/control\", {\"type\": \"" type "\", \"name\": escape(this.id), \"command\": \"" cmd-name "\"});
            showStatusMessage('" msg "');
	    return false;
	}); });
"]))

(defn header-js []
     (html (include-css "/css/searchlight.css")
	   (include-css "/css/global.css")
	   (include-css "/css/playback.css")
	   (js "jquery-1.7.1.min")
	   (js "jquery.tmpl")
	   (js "jquery.json-2.2.min")
	   (js "comet")
	   (js "jquery.searchlight")
	   (js "notification")
	   (js "header")))

(defn top-header []
  (str (html (spacing-a (map #(link-to (:link %) (:name %)) [{:link "/artists" :name "Artists"}
						  {:link "/genres" :name "Genres"}
						  {:link "/covers" :name "Covers"}
						  {:link "/playlist" :name "Playlist"}]))
	     "<span id=\"search-container\">Search: <input id=\"search\" autocomplete=\"off\"></span>"
	     (template "playback")
	     (html
	      [:table 
	       [:tr
		[:td [:p {:id "playback-art"}]]
		[:td
		 [:p {:id "playback"}]
		 [:p {:id "playback-control"}]]]])
	     )))

(defn comet-load [name id-name]
  (str "$(document).ready( function() { $.comet(function(data) { " name " = $.evalJSON(data); update_" name "(); }, " id-name "); });"))

(defn init-json-data [dataref filter-func name]
  (str
   (cond filter-func
      (str "var " name " = " (json/json-str (filter-func @dataref)) ";")
     :else
      (str "var " name " = " (json/json-str @dataref) ";"))
   (str "var " name "id = " (long-polling/register-polling dataref filter-func) ";")))

(defn long-polling-scripts [name ref filter-func]
  (let [register (init-json-data ref filter-func name)
	comet (comet-load name (str name "id"))]
    (html [:script {:type "text/javascript"} register]
	  (js (str "list-" name))
	  [:script {:type "text/javascript"} comet])))

(defn playback-status []
  (let [playing-track #(when (> (count @playlist/tracks) 0)
			 (nth @playlist/tracks (audio-player/playlist-index)))
	status #(let [track (playing-track)]
		 (cond (and (audio-player/is-playing) track)
		       (format "<b>%s:</b> %s %s / %s" 
			       (cond (audio-player/is-paused) "Paused"
				     :else "Playing")
			       (audio/format-track track)
			       (audio/format-length %)
			       (audio/format-length (:length track)))
		       :else "Not playing :("))
	art #(let [album (:album (playing-track))
		   artist (:artist (playing-track))]
	       (when (and album artist)
		 (audio/get-album-cover artist album)))
	format-playback #(hash-map :index (audio-player/playlist-index)
				   :love (:loved (playing-track))
				   :art (art)
				   :status (status (% :player-position)))]
    (long-polling-scripts "playback" audio-player/state
			  format-playback)))

(defn list-covers []
  (html4
   [:head 
    [:title "Covers"]
    (header-js)
    (include-css "/css/zoomer.css")
    (js "zoomer")
    (js "zoomer-load")
    (js "jquery.lazyload")
    (playback-status)
    ]
   [:body 
    (top-header)
    [:h1 "Covers"]
    "<ul class=\"thumb\">"
    (for [album @audio/albums]
      [:li (link-to (str "/artist/" (:artist album)) [:img {:original (:large-cover album) :src "" :alt (:name album) }])]
      )
    "</ul>"
    ]
   ))

(defn list-artists []
  (html4
   [:head 
    [:title "Artists"]
    (header-js)
    (playback-status)
    (long-polling-scripts "artists" audio/artists nil)]
   [:body 
    (template "artists")
    (top-header)
    [:h1 "Artists"]
    [:p {:id "artists"}]]
   ))

(defn filter-tracks-format [track]
  (hash-map 
   :track (:album-track track) 
   :length (audio/format-length (:length track)) 
   :name (audio/format-track-simple track) 
   :id (:filename track) 
   :loved (:loved track)))

(defn get-tracks-given-album [tracks album-name]
  (map filter-tracks-format (sort audio/sort-tracks (filter #(= (:album %) album-name) tracks))))

; list of array of album-name, list of tracks
(defn get-albums-with-tracks [artist-name tracks]
  (let [artist-tracks (filter #(= (:artist %) artist-name) tracks)
	albums (sort audio/sort-albums (set (map :album artist-tracks)))]
    (map #(hash-map :id % :tracks (get-tracks-given-album tracks %)) albums)))

(defn artist [title]
  (let [artist (first (filter #(= (:name %) title) @audio/artists)), 
	title (str "Artist - " (:name artist)),
	filter-tracks #(get-albums-with-tracks (:name artist) (vals %)),
	filter-albums-artist #(= (:artist %) (:name artist)),
	filter-albums #(filter filter-albums-artist %)]
    (html4
      [:head [:title title]
       (include-css "/css/tracks.css")
       (header-js)
       (playback-status)
       (long-polling-scripts "tracks" audio/tracks filter-tracks)
       (long-polling-scripts "albums" audio/albums filter-albums)]
      [:body 
       (template "tracks")
       (template "albums")
       (top-header)
       [:h1 title]
       (async-link "love" "enqueue-artist" (:name artist) "enqueue all loved tracks" "enqueued all loved tracks")
       [:h3 "Genres:"]
       (spacing-a (map #(link-to (str "/genre/" %) %) (:genres artist)))
       [:h3 "Albums:"]
       [:table {:id "albums" :class "albums"}]"</table>"
       [:h3 "Tracks:"]
       [:table {:id "tracks" :class "tracks"}]"</table>"]
     )))

(defn list-genres []
  (let [filter-genres-to-map #(hash-map :tag (nth % 0) :count (nth % 1))
	filter-genres #(map filter-genres-to-map 
			    (seq/frequencies (seq/flatten (map :genres %))))]
    (html4
     [:head 
      [:title "Genres"]
      (header-js)
      (js "jquery.tagcloud-2")
      (playback-status)
      (long-polling-scripts "genres" audio/artists filter-genres)
      ]
     [:body 
      (top-header)
      [:h1 "Genres"]
      [:p {:id "genres"}]]
     )))

(defn genre [genre]
  (let [title (str "Genre - " genre)
	filter-genre #(some #{genre} (:genres %))
	filter-artist-genre #(sort-by :name (filter filter-genre %))]
    (html4
     [:head [:title title]
      (header-js)
      [:script {:type "text/javascript"} (str "var genre = \"" genre "\";")]
      (template "genre")
      (template "artists")
      (js "genre-links")
      (js "list-artists")
      (playback-status)
      (long-polling-scripts "artists" audio/artists filter-artist-genre)
      ]
     [:body
      (top-header)
      [:h1 title]
      [:p {:id "play-enqueue-links"}]
      [:p [:h3 "Artists:"]
       [:p {:id "artists"}]]
      ])))

(defn playlist []
  (let [filter-tracks-format #(hash-map 
			       :track (:album-track %) 
			       :length (audio/format-length (:length %)) 
			       :artist (:artist %)
			       :name (:title %)
			       :loved (:loved %)
			       :id (:filename %)),
	filter-tracks #(map filter-tracks-format %)]
    (html4
     [:head 
      [:title "Playlist"]
      (include-css "/css/playlist.css")
      (header-js)
      (long-polling-scripts "playlist" playlist/tracks filter-tracks)]
     (playback-status)
     [:body 
      (template "playlist")
      (top-header)
      [:h1 "Playlist"]
      (async-link "" "clear-playlist" "" "clear playlist" "playlist emptied") 
      "<br/><br/>"
      [:table {:id "playlist" :class "tracks"}]"</table>"
      ])))

(defn activity [id]
  (long-polling/get-msg id))

(defn when-handle-command [cmd data]
  (println (str "handling command:" cmd))
  (when (= cmd "play")
    (playlist/clear))
  (println "adding collection")
  (playlist/add-collection data)
  (when (= cmd "play")
    (println "playing")
    (audio-player/play-track-i 0)))

(defn handle-command [cmd params]
  (cond 
    (= cmd "play-track") (audio-player/play-track-i (new Integer (params "name")))
    (and (= cmd "play") (= (params "type") "")) (audio-player/play)
    (= cmd "pause") (audio-player/pause)
    (= cmd "stop") (audio-player/stop)
    (= cmd "prev-track") (audio-player/prev-track)
    (= cmd "next-track") (audio-player/next-track)
    (= cmd "remove") (playlist/remove-track-i (new Integer (params "name")))
    (= cmd "clear-playlist") (playlist/clear)
    (= cmd "love-track") (audio-player/love-track)
    (= cmd "update-album") (audio/update-album-online-references (url-decode (params "name")))
    :else
    (let [decoded-name (url-decode (params "name"))]
      (cond (= (params "type") "love")
	    (let [only-loved #(filter :loved %)]
	      (cond (= cmd "enqueue-genre")
		    (playlist/add-collection (only-loved (audio/tracks-by-genre decoded-name)))
		    (= cmd "enqueue-album")
		    (playlist/add-collection (only-loved (audio/tracks-by-album decoded-name)))
		    (= cmd "enqueue-artist")
		    (playlist/add-collection (only-loved (audio/tracks-by-artist decoded-name)))))
	    (= (params "type") "genre")
	    (when-handle-command cmd (audio/tracks-by-genre decoded-name))
	    (= (params "type") "artist")
	    (when-handle-command cmd (audio/tracks-by-artist decoded-name))
	    (= (params "type") "album")
	    (when-handle-command cmd (audio/tracks-by-album decoded-name))
	    (= (params "type") "track")
	    (when-handle-command cmd (@audio/tracks decoded-name))))
    :else "command not found")
  "")

(defn icontains? [s substring]
  (str/contains? (. s toLowerCase) (. substring toLowerCase)))

(defn search [search]
  (let [genres (filter #(icontains? % search) (sort (set (seq/flatten (map :genres @audio/artists)))))
	artists (sort (map :name (filter #(icontains? (:name %) search) @audio/artists)))
	albums (filter #(icontains? (:name %) search) @audio/albums)
	tracks (filter #(icontains? (:title %) search) (vals @audio/tracks))]
    (json/json-str [
		    {:title "Genres" :results (map #(vector (str "/genre/" %), %, "") (take 5 genres))}
		    {:title "Artists" :results (map #(vector (str "/artist/" %), %, "") (take 5 artists))}
		    {:title "Albums" :results (map #(vector (str "/artist/" (:artist %) "#a" (:name %)), (:name %), (:cover %)) (take 5 albums))}
		    {:title "Tracks" :results (map #(vector (str "/artist/" (:artist %) "#a" (:album %)), (audio/format-track %), "") (take 5 tracks))}
		    ])))

(defn mobile-header-js []
  (html 
   "<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">"
   (include-css "/css/jquery.mobile.min.css")
   (js "jquery-1.7.1.min")
   (js "jquery.mobile.min")
   (js "notification")
   ))

(defn mobile-header [title]
  (html
   [:div {:data-role "header"}
    [:h1 title]]))

(defn mobile-header-with-back [title]
  (html
   [:div {:data-role "header"}
    [:a {:href "" :data-rel "back" :data-icon "arrow-l"} "Back"]
    [:a {:href "/mobile/artists" :data-ajax "false" :data-icon "arrow-u"} "Artists"]
    [:h1 title]]))

(defn mobile-footer [option]
  (let [options (list {:name "artists" :url @last-artists-url} {:name "playlist" :url "/mobile/playlist" } {:name "playback" :url "/mobile/playback"})]
    (html
     [:div {:data-role "footer" :data-position "fixed" :data-theme "b"}
      [:div {:data-role "navbar"}
       [:ul
       (map #(cond (= % option)
                   (html [:li [:a {:href (:url %) :class "ui-btn-active" :rel "external"} (:name %)]])
                   :else
                   (html [:li [:a {:href (:url %) :rel "external"} (:name %)]])) options)
       ]]])))

(defn add-dividers-and-format [list format-func]
  (let [letters (ref '())]
    (for [element list]
      (let [name (:name element)
	    letter (first name)]
	(cond (not (some #{letter} @letters))
	      (do 
		(common/ref-upd-nil letters (conj @letters letter))
		(str (html [:li {:data-role "list-divider"} letter]) (format-func element)))
	    :else
	    (format-func element))))))

(def last-artists-url (ref ""))

(defn set-last-artists-url [url]
  (common/ref-upd-nil last-artists-url url))

(defn list-mobile-artists []
  (set-last-artists-url "/mobile/artists/")
  (html5
   [:head 
    [:title "Artists"]
    (mobile-header-js)
    ]
   [:body
    [:div {:data-role "page"}
     (mobile-header "Artists")
     [:div {:data-role "content"}
      [:ul {:data-role "listview" :data-inset "true"}
       (let [format-artist #(html [:li (link-to {:data-ajax "false"} (str "/mobile/artist/" (url-encode (:name %))) (:name %))])]
        (add-dividers-and-format (sort-by :name @audio/artists) format-artist))
       ]]
     (mobile-footer "artists")
     ]]
   ))

(defn mobile-artist [name]
  (set-last-artists-url (str "/mobile/artist/" name))
  (let [artist (first (filter #(= (:name %) name) @audio/artists)), 
	title (str "Artist - " (:name artist))]
    (html5
     [:head 
      [:title title]
      (mobile-header-js)
      (js "mobile-albums")
      ]
     [:body 
      [:div {:data-role "page" :data-add-back-btn "true"}
       (mobile-header-with-back title)
       [:div {:data-role "content"}
	[:ul {:data-role "listview" :data-inset "true"}
	 (map #(html [:li [:a {:href (str "/mobile/album/" (url-encode (:artist %)) "/" (url-encode (:name %)) "/") :class "go" :data-ajax "false" :name (url-encode (:name %))} [:img {:src (:large-cover %)}] (:name %)] [:a {:href "" :class "play" :data-theme "c" :name (url-encode (:name %))} "Play album"]]) (filter #(= (:artist %) (:name artist)) @audio/albums))
	 ]]
       (mobile-footer "artists")]
      [:div {:data-role "dialog" :id "dialog"}
       [:div {:data-role "header"} [:h1 "Enqueue album?"]]
       [:div {:data-role "content"}
	 [:a {:href "" :id "enqueue-album-ok" :data-rel "back" :data-role "button"} "Enqueue"]
	 [:a {:href "" :id "enqueue-album-cancel" :data-rel "back" :data-role "button" :data-theme "c"} "Cancel"]
	 ]]
      ]
     )))

(defn mobile-artist-album [artist album]
  (set-last-artists-url (str "/mobile/album/" artist "/" album "/"))
  (let [title (str "Album - " artist " - " album)]
    (html5
     [:head 
      [:title title]
      (mobile-header-js)
      (js "mobile-tracks")
      ]
     [:body 
      [:div {:data-role "page" :data-add-back-btn "true"}
       (mobile-header-with-back title)
       [:div {:data-role "content"}
	[:ul {:data-role "listview" :data-inset "true"}
	 (let [filter-tracks #(and (= (:artist %) artist) (= (:album %) album))
	       format-track #(html [:li [:a {:href "" :onclick (str "enqueue_track('" (url-encode (:filename %)) "'); return false;") } (str (:artist %) " - " (:title %))]])]
	   (map format-track (filter #(filter-tracks %) (vals @audio/tracks))))
	 ]]
       (mobile-footer "artists")]]
     )))

; TODO:
;
; select mode i playlist til delete

(defn list-mobile-playlist []
  (html5
   [:head 
    [:title "Playlist"]
    (mobile-header-js)
    (js "comet")
    (js "jquery.json-2.2.min")
    (js "mobile-playlist")
    (long-polling-scripts "mobile_playlist" audio-player/state
			  (fn [_] (hash-map :index (audio-player/playlist-index))))
    ]
   [:body 
    [:div {:data-role "page"}
     (mobile-header "Playlist")
     [:div {:data-role "content"}
      [:ol {:data-role "listview"}
       (let [format-track #(html [:li [:a {:href "" :onclick (str "play_track('" %1 "'); return false;")} (str (:artist %2) " - " (:title %2))]])]
	 (map-indexed format-track @playlist/tracks))
       ]]
     (mobile-footer "playlist")]]
   ))

(defn mobile-playback-status []
  (let [playing-track #(when (> (count @playlist/tracks) 0)
			 (nth @playlist/tracks (audio-player/playlist-index)))
	status #(let [track (playing-track)]
		 (cond (and (audio-player/is-playing) track)
		       (format "<b>%s:</b> %s / %s" 
			       (cond (audio-player/is-paused) "Paused"
				     :else "Playing")
			       (audio/format-length %)
			       (audio/format-length (:length track)))
		       :else "Not playing :("))
	art #(let [album (:album (playing-track))
		   artist (:artist (playing-track))]
	       (when (and album artist)
		 (audio/get-album-cover artist album)))
	track-info #(let [track (playing-track)]
		      (when track
			:track (audio/format-track track)))
	format-playback #(hash-map :index (audio-player/playlist-index)
				   :love (:loved (playing-track))
				   :art (art)
				   :paused (audio-player/is-paused)
				   :playing (audio-player/is-playing)
				   :track (track-info)
				   :status (status (% :player-position)))]
    (long-polling-scripts "mobile_playback" audio-player/state
			  format-playback)))

(defn mobile-playback-control []
  (html5
   [:head 
    [:title "Playback control"]
    (mobile-header-js)
    (js "jquery.json-2.2.min")
    (js "comet")
    (mobile-playback-status)
    (js "mobile-playback")
    (include-css "/css/mobile.css")
    ]
   [:body 
    [:div {:data-role "page"}
     (mobile-header "Playback control")
     [:div {:data-role "content"}
      [:div {:class "center"}
       [:img {:src "" :id "img"}]
       [:p {:id "playback-status" :class "center"}]
       [:p {:id "playback-info" :class "center"}]]
      [:center
       [:a {:href "" :data-role "button" :data-inline "true" :id "playback-prev"} "prev"]
       [:a {:href "" :data-role "button" :data-inline "true" :id "playback-toggle"} "pause"]
       [:a {:href "" :data-role "button" :data-inline "true" :id "playback-next"} "next"]
       [:br]
       [:a {:href "" :data-role "button" :data-inline "true" :id "playback-stop"} "stop"]
       [:a {:href "" :data-role "button" :data-inline "true" :id "playback-love"} "love"]]
      ]
     (mobile-footer "playback")]]
   ))

(defroutes my-routes
  (GET "/mobile/artists" []
    (list-mobile-artists)) ; list-mobile-artists-album
;  (GET ["/mobile/artist-json/:id" :id #".*"] [id]
;    (mobile-artist-json id))
  (GET ["/mobile/artist/:id" :id #".*"] [id]
    (mobile-artist id))
  (GET ["/mobile/album/:artist/:album/" :artist #".*" :album #".*"] [artist album]
    (mobile-artist-album artist album))
  (GET "/mobile/playlist" []
    (list-mobile-playlist))
  (GET "/mobile/playback" []
    (mobile-playback-control))
  (GET ["/activity/:id" :id #".*"] [id]
    (activity (new Integer id)))
  (GET "/artists" []
    (list-artists))
  (GET "/covers" []
    (list-covers))
  (GET "/genres" []
    (list-genres))
  (GET "/playlist" []
    (playlist))
  (POST "/control" {params :params}
    (handle-command (params "command") params))
  (GET "/" []
    (list-artists))
  (GET ["/artist/:id" :id #".*"] [id]
    (artist id))
  (GET ["/genre/:id" :id #".*"] [id]
    (genre id))
  (GET "/search" {params :params}
    (search (params "q")))
  (GET ["/template/:filename" :filename #".*"] [filename]
    (load-static-file))
  (GET ["/js/:filename" :filename #".*"] [filename]
    (load-static-file))
  (GET ["/css/:filename" :filename #".*"] [filename]
    (load-static-file))
  (GET ["/img/:filename" :filename #".*"] [filename]
    (load-static-file))
  (GET ["/images/:filename" :filename #".*"] [filename]
    (load-static-file))
  (route/not-found (html [:h1 "Page not found!"])))

;(run-jetty (var my-routes) {:port 6666})
(run-jetty (var my-routes) {:port 5000})
